package com.logica.mdm.webfrontend.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.displaytag.pagination.PaginatedList;
import org.displaytag.properties.SortOrderEnum;

import com.logica.mdm.webfrontend.dao.SearchPartyDao;
import com.logica.mdm.webfrontend.dao.beans.PartyResultBean;
import com.logica.mdm.webfrontend.models.party.PartySearchResult;
import com.logica.mdm.webfrontend.models.party.PartySearchUtils;

/**
 * DisplayTag paginated list implementation for use with JDBC search requests.
 * @param <T> Record model class, accepting a Record in its constructor
 */
public abstract class JdbcPaginatedList implements PaginatedList {

	protected boolean firstRequestDone = false;
	protected boolean refreshRequired = true;
	protected SearchPartyDao searchPartyDao;
	protected RequestWithParamsBean searchRequest;
	
	protected int fullListSize = 0;
	protected int objectsPerPage = 20;
	protected int pageNumber = 1;
	protected String searchId; // Search token of the first request
	protected List<PartySearchResult> list;
	protected String sortCriterion;
	protected SortOrderEnum sortDirection = SortOrderEnum.ASCENDING;
	
	/**
	 * Construct a paginated list from a search request.
	 * @param siperianClientPool a TargetSource to get a SiperianClient from
	 * @param searchRequest SiperianClient SearchRequestBase (e.g. SearchQueryRequest)
	 */
	public JdbcPaginatedList(SearchPartyDao searchPartyDao, RequestWithParamsBean searchRequest) {
		if (searchPartyDao == null) {
			throw new IllegalArgumentException("The 'searchPartyDao' argument cannot be null");
		}
		if (searchRequest == null) {
			throw new IllegalArgumentException("The 'searchRequest' argument cannot be null");
		}
		
		this.searchPartyDao = searchPartyDao;
		this.searchRequest = searchRequest;
		
		String[] criteria = StringUtils.split(searchRequest.getSortCriteria(), ',');
		if (criteria.length > 0) {
			String[] criterion = StringUtils.split(criteria[0].trim(), ' ');
			this.sortCriterion = columnToProperty(criterion[0]);
			if (criterion.length > 1) {
				this.sortDirection = SortOrderEnum.fromName(criterion[1].toLowerCase() + "ending");
			}
		}
		
	}
	
	/**
	 * Perform the search request.
	 * This will either call GetSearchResults if a searchToken is set,
	 * or a subclass of SearchRequestBase if no search has been performed yet.
	 */
	protected void doRequest() {
		if (searchId != null) {
			// Paginated search continuation
			int firstRecord = (pageNumber - 1) * objectsPerPage;
			searchRequest.setFirstRecord(firstRecord);
			searchRequest.setLastRecord(firstRecord + objectsPerPage);
			
			List<PartyResultBean> responses = searchPartyDao.searchParty(PartySearchUtils.buildPaginatedOrderedQuery(searchRequest), searchRequest.getParams());
			this.list = recordsToModels(responses);
			this.refreshRequired = false;
		} else {
			// Initial search request
			this.fullListSize = searchPartyDao.countParty(PartySearchUtils.buildCountQuery(searchRequest), searchRequest.getParams());
			
			this.firstRequestDone = true;
			this.searchId = UUID.randomUUID().toString(); //Unique identifier
			
			this.pageNumber = 1;
			this.refreshRequired = true;
		}
	}
	
	/**
	 * Convert a list of PartyResultBean records to a list of 
	 * concrete PartySearchResult record models.
	 * @param records
	 * @return list of PartySearchResult record models
	 */
	protected List<PartySearchResult> recordsToModels(List<PartyResultBean> records) {
		List<PartySearchResult> list = new ArrayList<PartySearchResult>();
		if (records != null) {
			for (PartyResultBean record : records) {
				list.add(recordToModel(record));
			}
		}
		return list;
	}
	
	/**
	 * Convert aPartyResultBean record to a concrete 
	 * PartySearchResult record model.
	 * This method may be overridden to use a different constructor
	 * or to set additional properties on the model.
	 * @param record
	 * @return record model
	 */
	protected PartySearchResult recordToModel(PartyResultBean record) {
		return new PartySearchResult(record);
	}

	/**
	 * Convert a propertyName to a COLUMN_NAME.
	 * @param propertyName propertyName in camelCase
	 * @return COLUMN_NAME
	 */
	protected String propertyToColumn(String propertyName) {
		return StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(propertyName), "_").toUpperCase();
	}
	
	/**
	 * Convert a COLUMN_NAME to a propertyName.
	 * @param columnName COLUMN_NAME
	 * @return propertyName
	 */
	protected String columnToProperty(String columnName) {
		return StringUtils.uncapitalize(StringUtils.remove(WordUtils.capitalizeFully(columnName, new char[] { '_' }), '_'));
	}

	/**
	 * Get the full list size.
	 * This will perform the first SIF request if not already done.
	 * @return full list size
	 */
	@Override
	public int getFullListSize() {
		if (!firstRequestDone) {
			doRequest();
		}
		return fullListSize;
	}

	/**
	 * Get the number of records per page.
	 * @return number of objects per page.
	 */
	@Override
	public int getObjectsPerPage() {
		return objectsPerPage;
	}

	/**
	 * Get the page number to display.
	 * @return page number
	 */
	@Override
	public int getPageNumber() {
		return pageNumber;
	}

	/**
	 * Set the page number to display.
	 * This may invalidate the current search results if a change is detected.
	 * @param pageNumber new page number
	 */
	public void setPageNumber(int pageNumber) {
		int pageMax = (int) Math.ceil((double)getFullListSize() / getObjectsPerPage());
		if (pageNumber < 1) {
			pageNumber = 1;
		}
		if (pageMax < 1) {
			pageMax = 1;
		}
		if (pageNumber > pageMax) {
			pageNumber = pageMax;
		}
		
		if (pageNumber != this.pageNumber) {
			this.refreshRequired = true;
		}
		
		this.pageNumber = pageNumber;
	}

	/**
	 * Get a unique ID representing this search request.
	 * This will perform the first request if not already done.
	 * 
	 * Note: this searchId is the searchToken of the first SIF request.
	 * Subsequent requests may use a different searchToken, but this
	 * public searchId will NOT change, to ensure proper operation of
	 * DisplayTag and session handling.
	 * 
	 * @return unique search ID
	 */
	@Override
	public String getSearchId() {
		if (!firstRequestDone) {
			doRequest();
		}
		return searchId;
	}

	/**
	 * Get the records list.
	 * This will perform a request if a refresh is required
	 * (e.g. if the page number changed).
	 * @return records list
	 */
	@Override
	@SuppressWarnings("rawtypes")
	public List getList() {
		if (refreshRequired) {
			doRequest();
		}
		return list;
	}

	/**
	 * Get the sort criterion (property name).
	 * @return sort criterion
	 */
	@Override
	public String getSortCriterion() {
		return sortCriterion;
	}
	
	/**
	 * Set the sort criterion (property name).
	 * This may invalidate the current search results if a change is detected.
	 * @param sortCriterion
	 */
	public void setSortCriterion(String sortCriterion) {
		if (sortCriterion != null && !sortCriterion.equalsIgnoreCase(this.sortCriterion)) {
			String sortColumn = propertyToColumn(sortCriterion);
			String sortDir = this.sortDirection == SortOrderEnum.ASCENDING ? "ASC" : "DESC";
			this.searchRequest.setSortCriteria(sortColumn + " " + sortDir);
			this.refreshRequired = true;
		}
		this.sortCriterion = sortCriterion;
	}

	@Override
	public SortOrderEnum getSortDirection() {
		return sortDirection;
	}
	
	/**
	 * Set the sort direction (ascending or descending).
	 * This may invalidate the current search results if a change is detected.
	 * @param sortDirection
	 */
	public void setSortDirection(SortOrderEnum sortDirection) {
		if (sortDirection != this.sortDirection && this.sortCriterion != null) {
			String sortColumn = propertyToColumn(this.sortCriterion);
			String sortDir = sortDirection == SortOrderEnum.ASCENDING ? "ASC" : "DESC";
			this.searchRequest.setSortCriteria(sortColumn + " " + sortDir);
			this.refreshRequired = true;
		}
		this.sortDirection = sortDirection;
	}
	
	/**
	 * Parse standard HTTP Servlet Request arguments from DisplayTag.
	 * @param request
	 */
	public void parseRequestArgs(HttpServletRequest request) {
		String pageNum = request.getParameter("page");
		if (pageNum != null) {
			setPageNumber(Integer.parseInt(pageNum));
		}
		
		String sortCriterion = request.getParameter("sort");
		if (sortCriterion != null) {
			setSortCriterion(sortCriterion);
		}
		
		String sortDirection = request.getParameter("dir");
		if (sortDirection != null) {
			setSortDirection(SortOrderEnum.fromName(sortDirection + "ending"));
		}
	}

}
